package edu.utexas.esel.jgraphpad.archpadplugin.model;

import java.util.HashMap;
import java.util.Map;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.xml.transform.sax.TransformerHandler;

import org.xml.sax.SAXException;

import com.jgraph.editor.JGraphEditorResources;
import com.sun.xml.internal.bind.util.AttributesImpl;

import edu.utexas.esel.jgraphpad.archpadplugin.Utils;
import edu.utexas.esel.jgraphpad.archpadplugin.model.Element.ElementDeclaration;

/**
 * This class represents a node in an ArchPad tree model.
 *
 * @author svetoslavganov@mail.utexas.edu
 *
 */
public class ElementTreeNode extends DefaultMutableTreeNode {

	/**
	 * Attribute that uniquely identifies a root of a component/connector 
	 */
	public static final String ATTRIBUTE_ID = "id";

	/**
	 * The neighbors of an element.
	 */
	public static final String ATTRIBUTE_NEIGHBORS = "neighbors";

	/**
	 * The members of a region.
	 */
	public static final String ATTRIBUTE_MEMBERS = "members";

	/**
	 * The rationale applicable to each ArchPad tree model node.
	 */
	public static final String ATTRIBUTE_RATIONALE = "rationale";

	/**
	 * The id of the parent element.
	 */
	public static final String ATTRIBUTE_PARENT_ID = "parent";

	/**
	 * The name of a node representing a name. 
	 */
	public static final String NODE_NAME_NAME = "name";

	private static final long serialVersionUID = 1L;
	
	private static final StringBuilder sBuilder = new StringBuilder();

	private static final String STRING = "string";

	private ElementDeclaration mDeclaration;

	private Object mValue;

	private Map<String, String> mAttributes = new HashMap<String, String>();

	private static AttributesImpl sTempAttributes;

	/**
	 * Creates a new instance with the given {@link ElementDeclaration}.
	 *
	 * @param declaration The declaration.
	 */
	public ElementTreeNode(ElementDeclaration declaration) {
		mDeclaration = declaration;
		mAttributes = new HashMap<String, String>();
	}

	/**
	 * Gets the {@link ElementDeclaration} of the type represented by this node.
	 *
	 * @return The declaration.
	 */
	public ElementDeclaration getDeclaration() {
		return mDeclaration;
	}

	/**
	 * Sets the {@link ElementDeclaration} of the type represented by this node.
	 *
	 * @param declaration The declaration.
	 */
	public void setDeclaration(ElementDeclaration declaration) {
		mDeclaration = declaration;
	}

	/**
	 * Gets the attributes of the node.
	 *
	 * @return The attributes.
	 */
	public Map<String, String> getAttributes() {
		return mAttributes;
	}

	/**
	 * Gets the value of an attribute of the node.
	 *
	 * @param attribute The attribute.
	 * @return The attribute value.
	 */
	public String getAttribute(String attribute) {
		return mAttributes.get(attribute);
	}

	/**
	 * Sets the value of an attribute of the node.
	 * </p>
	 * Note: If the value is <code>null</code> the attribute is removed.
	 * 
	 * @param attribute The attribute.
	 * @param value The attribute value.
	 */
	public void setAttribute(String attribute, String value) {
		if (value != null) {
		    mAttributes.put(attribute, value);
	    } else {
	    	mAttributes.remove(attribute);
	    }
	}

	/**
	 * Gets the value of the node.
	 *
	 * @return The value.
	 */
	public Object getValue() {
		return mValue;
	}

	/**
	 * Sets the value of the node.
	 *
	 * @param value The value.
	 */
	public void setValue(Object value) {
		mValue = value;
	}

	@Override
	public String toString() {
		sBuilder.delete(0, sBuilder.length());
		if (STRING.equals(mDeclaration.getName())) {
			if (mValue != null) {
			    sBuilder.append(mValue);
			} else {
				sBuilder.append(JGraphEditorResources.getString(
					mDeclaration.getName()));
			}
		} else {
		    sBuilder.append(JGraphEditorResources.getString(
				mDeclaration.getName()));
		    if (mValue != null) {
			    sBuilder.append(" (");
			    sBuilder.append(mValue);
			    sBuilder.append(")");
		    }
		}
		return Utils.chopString(sBuilder.toString(), 30);
	}

	@Override
	public ElementTreeNode clone() {
		ElementTreeNode clone = (ElementTreeNode) super.clone();
		clone.mDeclaration = mDeclaration;
	    clone.mValue = mValue;
	    clone.mAttributes = new HashMap<String, String>();
		clone.mAttributes.putAll(mAttributes);
		// do not clone the id since it is unique for every instance
		clone.getAttributes().remove(ATTRIBUTE_ID);
		return clone;
	}

	/**
	 * Writes this node to XML using SAX. This implementation
	 * is recursive, thus storing all descendants.
	 *
	 * @param handler The handler used for writing.

	 * @throws SAXException If an error occurs while writing the node.
	 */
	public void writeToXMLViaSAX(TransformerHandler handler)
	        throws SAXException {

		if (sTempAttributes == null) {
			sTempAttributes = new AttributesImpl();
		} else {
		    sTempAttributes.clear();
		}

		for (Map.Entry<String, String> e : mAttributes.entrySet()) {
			String value = e.getValue();
			if (value != null && !value.isEmpty()) {
			    sTempAttributes.addAttribute("", "", e.getKey(), "CDATA",
				    e.getValue());
			}
		}

		String type = mDeclaration.getName();
		handler.startElement("", "", type, sTempAttributes);

		if (mValue != null) {
			String valueString = mValue.toString();
			handler.characters(valueString.toCharArray(), 0,
				valueString.length());
		}

		for (int i = 0, count = getChildCount(); i < count; i++) {
			ElementTreeNode child = (ElementTreeNode) getChildAt(i);
			child.writeToXMLViaSAX(handler);
		}

		handler.endElement("", "", type);
	}
}
